﻿using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using Netbits.Common.Collections;
using Netbits.Common.General;
using Netbits.Common.Network;
using Netbits.TelnetServer.Enums;

namespace Netbits.TelnetServer
{
	/// <summary>
	/// Represents the basic telnet-client-session client
	/// Implemented RFCs.
	///   RFC  727 (Telnet Logout)
	///   RFC  854 (Telnet Protocol)
	///   RFC  855 (Telnet Options)
	///   RFC  857 (Telnet Echo)
	///   RFC  858 (Telnet Suppress Go Ahead)
	///   RFC 1073 (Telnet Option WindowSize)
	///   RFC 1184 (LineMode Option) PARTIAL IMPLEMENTED
	/// 
	/// TODO: NOT implemented RFCs.
	///   RFC 1091 (TerminalType)
	///   RFC 1079 (TerminalSpeed)
	///   RFC 1096 (X Display Location)
	///   RFC 1572 (Telnet Option environment)
	///   RFC 1123 (ToDo: at page 16)
	///   RFC 2941 (Telnet Option authentication)
	///   RFC 2942 (Telnet Option authentication Kerberos 5)
	///   RFC 2943 (Telnet Option authentication DSA)
	///   RFC 2946 (Telnet Option data encryption)
	/// </summary>
	public abstract class BasicTelnetClientSession : TcpServerSession
	{
		#region Private Enumeration

		enum EnumOptionState
		{
			Unknown,
			False,
			True
		}

		#endregion  

		#region Private Variables

		readonly LockedDictionary<EnumTelnetOption, bool> _OptionStates;
		readonly ByteBuffer _Buffer;

		int _TelnetInitDone;
		int _Echoed;
		int _SuppressGoAhead;
		int _WindowSize;
		int _ClientHeight;
		int _ClientWidth;
		int _LinemodeIsSet;

		EnumTelnetLinemodeModes _LineMode_Mode = EnumTelnetLinemodeModes.Edit;
		EnumTelnetParserState _State = EnumTelnetParserState.Data;

		#endregion

		#region Constructors

		/// <summary>
		/// Create a new instance of BasicTelnetClientSession. 
		/// The instance is created by TCP-server
		/// </summary>
		/// <param name="Client_">The TCP-Client instance to handle.</param>
		public BasicTelnetClientSession( System.Net.Sockets.TcpClient Client_ ) : base( Client_ )
		{
			_Buffer = new ByteBuffer();
			_OptionStates = new LockedDictionary<EnumTelnetOption, bool>();
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Get or set if the server must echoed the char
		/// </summary>
		public bool Echoed
		{
			get
			{
				return _Echoed > 0;
			}
			set
			{				
				Interlocked.Exchange( ref _Echoed, (value == true ? 1 : 0) );
				if( _TelnetInitDone > 0 )
				{
					if( _Echoed > 0 )
						_SendWill( EnumTelnetOption.Echo );
					else
						_SendWont( EnumTelnetOption.Echo );
				}
			}
		}

		/// <summary>
		/// Get or set if the server wait for LF/CR
		/// </summary>
		public bool SuppressGoAhead
		{
			get
			{
				return _SuppressGoAhead > 0;
			}
			set
			{
				Interlocked.Exchange( ref _SuppressGoAhead, (value == true ? 1 : 0) );
				if( _TelnetInitDone > 0 )
				{
					if( _SuppressGoAhead > 0 )
					{
						_SendWill( EnumTelnetOption.SuppressGoAhead );
						_SendDo( EnumTelnetOption.SuppressGoAhead );
					}
					else
					{
						_SendWont( EnumTelnetOption.SuppressGoAhead );
						_SendDont( EnumTelnetOption.SuppressGoAhead );
					}
				}
			}
		}

		/// <summary>
		/// Get or set the client-linemode handling
		/// </summary>
		public EnumTelnetLinemodeModes LineMode_Modus
		{
			get
			{
				return _LineMode_Mode;
			}
			set
			{
				_LineMode_Mode = value;
				Interlocked.Exchange( ref _LinemodeIsSet, 1 );
			}
		}

		/// <summary>
		/// Get or set to handle Window-Size changes.
		/// </summary>
		public bool WindowSize
		{
			get
			{
				return _WindowSize > 0;
			}
			set
			{
				Interlocked.Exchange( ref _WindowSize, (value == true ? 1 : 0) );
				if( _TelnetInitDone > 0 )
				{
					if( _WindowSize > 0 )
						_SendWill( EnumTelnetOption.WindowSize );
					else
						_SendWont( EnumTelnetOption.WindowSize );
				}
			}
		}

		/// <summary>
		/// Get the client height
		/// </summary>
		public int ClientHeight
		{
			get
			{
				return _ClientHeight;
			}
		}

		/// <summary>
		/// Get the client width.
		/// </summary>
		public int ClientWidth
		{
			get
			{
				return _ClientWidth;
			}
		}

		#endregion

		#region Protected Functions

		protected virtual void _HandleChar( byte Char_ )
		{
		}

		protected virtual void _HandleWindowSize( int Width_, int Height_ )
		{
		}

		protected virtual void _HandleInit()
		{
		}

		protected void _SendText( string Text_, params object[] Arguments_ )
		{
			_SendText( Encoding.ASCII, Text_, Arguments_ );
		}

		protected void _SendText( Encoding Encoder_, string Text_, params object[] Arguments_ )
		{
			if( string.IsNullOrEmpty( Text_ ) )
				return;

			if( Encoder_ == null )
				throw new ArgumentNullException( "Encoder_" );

			string text = Text_;
			if( Arguments_ != null )
				text = string.Format( Text_, Arguments_ );

			_SendRaw( Encoder_.GetBytes( text ) );
		}

		#endregion

		#region Overrides

		protected sealed override void __Receive( byte[] Data_, int Size_ )
		{
			_ParseBuffer( Data_, Size_ );			
		}

		protected override void __ClientInitDone()
		{
			_SendTelnetInitial();
		}

		#endregion

		#region Private Functions

		#region Send Functions

		void _SendTelnetInitial()
		{
			if( _TelnetInitDone > 0 )
				return;

			if( _Echoed > 0 )
				_SendWill( EnumTelnetOption.Echo );
			else
				_SendWont( EnumTelnetOption.Echo );

			if( _SuppressGoAhead > 0 )
			{
				_SendWill( EnumTelnetOption.SuppressGoAhead );
				_SendDo( EnumTelnetOption.SuppressGoAhead );
			}
			else
			{
				_SendWont( EnumTelnetOption.SuppressGoAhead );
				_SendDont( EnumTelnetOption.SuppressGoAhead );
			}

			if( _WindowSize > 0 )
				_SendDo( EnumTelnetOption.WindowSize );
			else
				_SendDont( EnumTelnetOption.WindowSize );

			if( _LinemodeIsSet > 0 )
				_SendDo( EnumTelnetOption.LineMode );

			_HandleInit();

			Interlocked.Exchange( ref _TelnetInitDone, 1 );
		}

		void _SendGetAllEnvironment()
		{
			Debug.WriteLine( "SND SUB ENV GET" );

			_SendRaw( new byte[] 
			{ 
				(byte)EnumTelnetCommand.Iac, 
				(byte)EnumTelnetCommand.Subnegotiation, 
				(byte)EnumTelnetOption.NewEnvironment, 
				(byte)EnumTelnetEnvironmentType.SEND, 
				(byte)EnumTelnetEnvironment.VAR, 
				(byte)EnumTelnetEnvironment.VALUE, 
				(byte)EnumTelnetEnvironment.USERVAR, 
				(byte)EnumTelnetEnvironment.ESC, 
				(byte)EnumTelnetCommand.Iac, 
				(byte)EnumTelnetCommand.EndSubnegotiation 
			} );
		}

		void _SendGetStatus()
		{
			_SendRaw( new byte[] 
			{ 
				(byte)EnumTelnetCommand.Iac, 
				(byte)EnumTelnetCommand.Subnegotiation, 
				(byte)EnumTelnetOption.Status, 
				1, 
				(byte)EnumTelnetCommand.Iac, 
				(byte)EnumTelnetCommand.EndSubnegotiation 
			} );
		}

		void _SendDo( EnumTelnetOption Option_ )
		{
			if( _GetState( Option_ ) == EnumOptionState.True )
				return;

			Debug.WriteLine( "SND DO   TelnetOption [" + Option_ + "]" );

			_SendRaw( new byte[] 
			{ 
				(byte)EnumTelnetCommand.Iac, 
				(byte)EnumTelnetCommand.Do, 
				(byte)Option_ 
			} );

			if( Option_ != EnumTelnetOption.NewEnvironment )
				_SetState( Option_, true );
		}

		void _SendDont( EnumTelnetOption Option_ )
		{
			if( _GetState( Option_ ) == EnumOptionState.False )
				return;

			Debug.WriteLine( "SND DONT TelnetOption [" + Option_ + "]" );

			_SendRaw( new byte[] 
			{ 
				(byte)EnumTelnetCommand.Iac, 
				(byte)EnumTelnetCommand.Dont, 
				(byte)Option_ 
			} );
			_SetState( Option_, false );
		}

		void _SendWill( EnumTelnetOption Option_ )
		{
			if( _GetState( Option_ ) == EnumOptionState.True )
				return;

			Debug.WriteLine( "SND WILL TelnetOption [" + Option_ + "]" );

			_SendRaw( new byte[] 
			{ 
				(byte)EnumTelnetCommand.Iac, 
				(byte)EnumTelnetCommand.Will, 
				(byte)Option_ 
			} );
			_SetState( Option_, true );
		}

		void _SendWont( EnumTelnetOption Option_ )
		{
			if( _GetState( Option_ ) == EnumOptionState.False )
				return;

			Debug.WriteLine( "SND WONT TelnetOption [" + Option_ + "]" );

			_SendRaw( new byte[] 
			{ 
				(byte)EnumTelnetCommand.Iac, 
				(byte)EnumTelnetCommand.Wont, 
				(byte)Option_ 
			} );
			_SetState( Option_, false );
		}

		void _SendRaw( byte[] Buffer_ )
		{
			if( Buffer_ == null )
				return;

			base.Send( Buffer_ );
		}

		#endregion

		#region Handle and Receive

		string _ToBufferString( byte[] Buffer_, int Count_ )
		{
			StringBuilder sb = new StringBuilder();
			for( int i = 0; i < Count_; i++ )
				sb.Append( Buffer_[i].ToString() + "," );
			return sb.ToString();
		}

		void _ParseBuffer( byte[] Buffer_, int Count_ )
		{
			if( Buffer_ == null || Buffer_.Length <= 0 || Count_ <= 0 )
				return;

			Debug.WriteLine( "RCV-BUF: size={0}, buf={1}", Count_, _ToBufferString( Buffer_, Count_ ) );

			int _bufferPos = 0;
			byte currentByte;

			while( _bufferPos < Count_ )
			{
				currentByte = Buffer_[_bufferPos++];
				if( _State == EnumTelnetParserState.Data )
				{
					if( currentByte == (byte)EnumTelnetOption.IAC )
					{
						_State = EnumTelnetParserState.IAC;
						continue;
					}

					_HandleChar( currentByte );
					continue;
				}
				else if( _State == EnumTelnetParserState.SG )
				{
					if( currentByte == (byte)EnumTelnetOption.IAC )
						_State = EnumTelnetParserState.IAC;
					else
						_Buffer.Append( currentByte );
				}
				else if( _State == EnumTelnetParserState.DO )
				{
					_HandleDo( currentByte );
					_State = EnumTelnetParserState.Data;
				}
				else if( _State == EnumTelnetParserState.DONT )
				{
					_HandleDont( currentByte );
					_State = EnumTelnetParserState.Data;
				}
				else if( _State == EnumTelnetParserState.WILL )
				{
					_HandleWill( currentByte );
					_State = EnumTelnetParserState.Data;
				}
				else if( _State == EnumTelnetParserState.WONT )
				{
					_HandleWont( currentByte );
					_State = EnumTelnetParserState.Data;
				}
				else if( _State == EnumTelnetParserState.IAC )
				{
					switch( currentByte )
					{
						case (byte)EnumTelnetOption.SE:
							_HandleSB();
							_Buffer.Clear();
							_State = EnumTelnetParserState.Data;
							break;
						case (byte)EnumTelnetOption.NOP:
							_State = EnumTelnetParserState.Data;
							break;
						case (byte)EnumTelnetOption.DM:
							_State = EnumTelnetParserState.Data;
							break;
						case (byte)EnumTelnetOption.BREAK:
							_State = EnumTelnetParserState.Data;
							break;
						case (byte)EnumTelnetOption.IP:
							_State = EnumTelnetParserState.Data;
							break;
						case (byte)EnumTelnetOption.AO:
							_State = EnumTelnetParserState.Data;
							break;
						case (byte)EnumTelnetOption.AYT:
							_State = EnumTelnetParserState.Data;
							break;
						case (byte)EnumTelnetOption.EC:
							_State = EnumTelnetParserState.Data;
							break;
						case (byte)EnumTelnetOption.EL:
							_State = EnumTelnetParserState.Data;
							break;
						case (byte)EnumTelnetOption.GA:
							_State = EnumTelnetParserState.Data;
							break;
						case (byte)EnumTelnetOption.SB:
							_State = EnumTelnetParserState.SG;
							break;
						case (byte)EnumTelnetOption.WILL:
							_State = EnumTelnetParserState.WILL;
							break;
						case (byte)EnumTelnetOption.WONT:
							_State = EnumTelnetParserState.WONT;
							break;
						case (byte)EnumTelnetOption.DO:
							_State = EnumTelnetParserState.DO;
							break;
						case (byte)EnumTelnetOption.DONT:
							_State = EnumTelnetParserState.DONT;
							break;
						case (byte)EnumTelnetOption.IAC:
							_State = EnumTelnetParserState.Data;
							break;
					}
				}
				else
					Debug.WriteLine( "received unknown char: " + currentByte );
			}
		}

		void _HandleSB()
		{
			if( _Buffer.Count <= 0 )
				return;

			EnumTelnetOption to = (EnumTelnetOption)_Buffer[0];
			switch( to )
			{
				case EnumTelnetOption.WindowSize:
					_HandleClientType();
					break;
				case EnumTelnetOption.Status:
					break;
				case EnumTelnetOption.NewEnvironment:
					_HandleNewEnvironment();
					break;
				default:
					Debug.WriteLine( "unknown SB!" );
					break;
			}
		}

		void _HandleNewEnvironment()
		{
			if( _Buffer.Count > 2 )
				return;

			EnumTelnetEnvironmentType etet = (EnumTelnetEnvironmentType)_Buffer[1];
			switch( etet )
			{
				case EnumTelnetEnvironmentType.INFO:
					Debug.WriteLine( Encoding.ASCII.GetString( _Buffer.ToArray(), 2, _Buffer.Count - 2 ) );
					break;
				case EnumTelnetEnvironmentType.IS:
					Debug.WriteLine( Encoding.ASCII.GetString( _Buffer.ToArray(), 2, _Buffer.Count - 2 ) );
					break;
				case EnumTelnetEnvironmentType.SEND:
					Debug.WriteLine( Encoding.ASCII.GetString( _Buffer.ToArray(), 2, _Buffer.Count - 2 ) );
					break;
			}
		}

		void _HandleClientType()
		{
			_ClientWidth = (short)(_Buffer[2] & 0xFF);
			_ClientWidth += (short)(_Buffer[1] << 8);
			_ClientHeight = (short)(_Buffer[4] & 0xFF);
			_ClientHeight += (short)(_Buffer[3] << 8);
			if( _ClientWidth <= 0 )
				_ClientWidth = 0;
			if( _ClientHeight <= 0 )
				_ClientHeight = 0;

			Debug.WriteLine( "RCV SET Window-Size width=" + _ClientWidth + " height=" + _ClientHeight );
			_HandleWindowSize( _ClientWidth, _ClientHeight );
		}

		void _HandleDo( byte Data_ )
		{
			EnumTelnetOption to = (EnumTelnetOption)Data_;
			switch( to )
			{
				case EnumTelnetOption.Logout:
					Debug.WriteLine( "RCV DO   {0}", to );
					_SendWill( EnumTelnetOption.Logout );
					break;
				case EnumTelnetOption.Echo:
					Debug.WriteLine( "RCV DO   {0}", to );
					if( _Echoed == 0 )
						_SendWont( EnumTelnetOption.Echo );
					else
						_SendWill( EnumTelnetOption.Echo );
					break;
				case EnumTelnetOption.SuppressGoAhead:
					Debug.WriteLine( "RCV DO   {0}", to );
					if( _SuppressGoAhead == 0 )
						_SendWont( EnumTelnetOption.SuppressGoAhead );
					else
						_SendWill( EnumTelnetOption.SuppressGoAhead );
					break;
				case EnumTelnetOption.Status:
					Debug.WriteLine( "RCV DO   {0}", to );
					_SendWill( EnumTelnetOption.Status );
					break;
				default:
					Debug.WriteLine( "RCV DO   TelnetOption [" + ((int)Data_) + "] " + to + " not implemented yet!" );
					_SendWont( to );
					break;
			}
		}

		void _HandleDont( byte Data_ )
		{
			EnumTelnetOption to = (EnumTelnetOption)Data_;
			switch( to )
			{
				default:
					Debug.WriteLine( "RCV DONT TelnetOption [" + ((int)Data_) + "] " + to + " not implemented yet!" );
					break;
			}
		}

		void _HandleWill( byte Data_ )
		{
			EnumTelnetOption to = (EnumTelnetOption)Data_;
			switch( to )
			{
				case EnumTelnetOption.SuppressGoAhead:
					Debug.WriteLine( "RCV WILL {0}", to );
					if( _SuppressGoAhead == 0 )
						_SendDont( EnumTelnetOption.SuppressGoAhead );
					else
						_SendDo( EnumTelnetOption.SuppressGoAhead );
					break;
				case EnumTelnetOption.WindowSize:
					Debug.WriteLine( "RCV WILL {0}", to );
					if( _WindowSize == 0 )
						_SendDont( EnumTelnetOption.WindowSize );
					else
						_SendDo( EnumTelnetOption.WindowSize );
					break;
				case EnumTelnetOption.NewEnvironment:
					Debug.WriteLine( "RCV WILL {0}", to );
					_SendDo( EnumTelnetOption.NewEnvironment );
					_SendGetAllEnvironment();
					break;
				case EnumTelnetOption.LineMode:
					{
						if( _LinemodeIsSet > 0 )
						{
							_SendRaw( new byte[] 
							{ 
								(byte)EnumTelnetCommand.Iac, 
								(byte)EnumTelnetCommand.Subnegotiation, 
								(byte)EnumTelnetOption.LineMode,
								(byte)1,
								(byte)_LineMode_Mode,
								(byte)EnumTelnetCommand.Iac,
								(byte)EnumTelnetCommand.EndSubnegotiation
							} );
						}
						else
							_SendDont( to );
					}
					break;
				default:
					Debug.WriteLine( "RCV WILL TelnetOption [" + ((int)Data_) + "] " + to + " not implemented yet!" );
					_SendDont( to );
					break;
			}
		}

		void _HandleWont( byte Data_ )
		{
			EnumTelnetOption to = (EnumTelnetOption)Data_;
			switch( to )
			{
				default:
					Debug.WriteLine( "RCV WONT TelnetOption [" + ((int)Data_) + "] " + to + " not implemented yet!" );
					break;
			}
		}

		EnumOptionState _GetState( EnumTelnetOption Option_ )
		{
			using( _OptionStates.SyncObject.Read() )
			{
				if( !_OptionStates.ContainsKey( Option_ ) )
					return EnumOptionState.Unknown;

				if( _OptionStates[Option_] )
					return EnumOptionState.True;
			}
			return EnumOptionState.False;
		}

		void _SetState( EnumTelnetOption Option_, bool State_ )
		{
			using( _OptionStates.SyncObject.Write() )
			{
				if( !_OptionStates.ContainsKey( Option_ ) )
					_OptionStates.Add( Option_, State_ );

				_OptionStates[Option_] = State_;
			}
		}

		#endregion

		#endregion
	}
}
